package main

import (
	"JoDocker/container"
	_ "JoDocker/nsenter"
	"encoding/json"
	"fmt"
	"github.com/Sirupsen/logrus"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

const ENV_EXEC_PID = "mydocker_pid"
const ENV_EXEC_CMD = "mydocker_cmd"

// ExecContainer 进入容器
func ExecContainer(containerName string, cmdSlc []string) {
	pid, err := getContainerPidByName(containerName)
	if err != nil {
		logrus.Errorf("Exec container getContainerPidByName %s error %v", containerName, err)
		return
	}
	cmdStr := strings.Join(cmdSlc, " ")
	logrus.Infof("container pid %s", pid)
	logrus.Infof("command %s", cmdStr)

	cmd := exec.Command("/proc/self/exe", "exec")
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// 设置pid和cmd的环境变量
	os.Setenv(ENV_EXEC_PID, pid)
	os.Setenv(ENV_EXEC_CMD, cmdStr)

	// 因exec命令其实是docker发起的另外一个进程，这个进程的父进程其实是宿主机，并不是容器。因为在Cgo里面使用了setns系统调用，
	// 才使得这个exec进程可以进入到容器内的命名空间中， 但是由于环境变量是继承自父进程的，因此这个exec进程的环境变量其实是继承自宿主机的，
	// 所以在exec进程内看到的环境变量其实就是宿主机的环境变量。 （只要是容器内pid为1的进程，创建出来的进程都会继承他的环境变量。
	// 将宿主机的环境变量肯容器胡环境变量一起放到exec的进程内，这样就可以获取到容器内的环境变量了
	envSlc := getEnvsByPid(pid)
	cmd.Env = append(os.Environ(), envSlc...)

	if err := cmd.Run(); err != nil {
		logrus.Errorf("Exec container %s error %v", containerName, err)
	}
}

// 从容器的配置文件中获取pid
func getContainerPidByName(containerName string) (string, error) {
	dirPath := fmt.Sprintf(container.DefaultInfoLocation, containerName)
	configFilePath := dirPath + container.ConfigFileName
	contentByteSlc, err := ioutil.ReadFile(configFilePath)
	if err != nil {
		return "", err
	}

	var containerInfo container.ContainerInfo
	if err := json.Unmarshal(contentByteSlc, &containerInfo); err != nil {
		return "", err
	}
	return containerInfo.Pid, nil
}

// 根据进程id获取环境变量
func getEnvsByPid(pid string) []string {
	envPath := fmt.Sprintf("/proc/%s/environ", pid)
	contentByteSlc, err := ioutil.ReadFile(envPath)
	if err != nil {
		logrus.Errorf("Read env file %s error %v", envPath, err)
		return nil
	}
	envSlc := strings.Split(string(contentByteSlc), "\u0000")
	return envSlc
}
